'
' * FILE:        WindowsLiveLogin.vb
' *                                                                      
' * DESCRIPTION: Sample implementation of Web Authentication and Delegated 
' *              Authentication protocol in VB. Also includes trusted 
' *              sign-in and application verification sample 
' *              implementations.
' *
' * VERSION:     1.1b
' *
' * Copyright (c) 2008 Microsoft Corporation.  All Rights Reserved.
' 


Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Collections.Specialized
Imports System.Collections
Imports System.Web
Imports System.Web.Configuration
Imports System.Security.Cryptography
Imports System.IO
Imports System.Net
Imports System.Reflection
Imports System.Xml

Namespace WindowsLive
	''' <summary>
	''' Sample implementation of Web Authentication and Delegated Authentication 
	''' protocol. Also includes trusted sign-in and application 
	''' verification sample implementations.
	''' </summary>
	Public Class WindowsLiveLogin
		''' <summary>
		''' Stub implementation for logging debug output. You can run
		''' a tool such as 'dbmon' to see the output.
		''' </summary>
		Private Shared Sub debug(ByVal msg As String)
			System.Diagnostics.Debug.WriteLine(msg)
			System.Diagnostics.Debug.Flush()
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' application ID and secret key.
		'''
		''' We recommend that you employ strong measures to protect
		''' the secret key. The secret key should never be
		''' exposed to the Web or other users.
		''' </summary>
		Public Sub New(ByVal appId As String, ByVal secret As String)
			Me.New(appId, secret, Nothing)
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' application ID, secret key, and security algorithm.
		'''
		''' We recommend that you employ strong measures to protect
		''' the secret key. The secret key should never be
		''' exposed to the Web or other users.
		''' </summary>
		Public Sub New(ByVal appId As String, ByVal secret As String, ByVal securityAlgorithm As String)
			Me.New(appId, secret, securityAlgorithm, False)
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' forceDelAuthNonProvisioned flag, policy URL, and return URL.
		''' 
		''' The 'force_delauth_nonprovisioned' flag indicates whether
		''' your application is registered for Delegated Authentication 
		''' (that is, whether it uses an application ID and secret key). We 
		''' recommend that your Delegated Authentication application always 
		''' be registered for enhanced security and functionality. 
		''' </summary>
		Public Sub New(ByVal forceDelAuthNonProvisioned As Boolean, ByVal policyUrl As String, ByVal returnUrl As String)
			Me.ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned
			Me.PolicyUrl = policyUrl
			Me.ReturnUrl = returnUrl
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' application ID, secret key, security algorithm and 
		''' forceDelAuthNonProvisioned flag.
		'''
		''' We recommend that you employ strong measures to protect
		''' the secret key. The secret key should never be
		''' exposed to the Web or other users.
		''' 
		''' The 'force_delauth_nonprovisioned' flag indicates whether
		''' your application is registered for Delegated Authentication 
		''' (that is, whether it uses an application ID and secret key). We 
		''' recommend that your Delegated Authentication application always 
		''' be registered for enhanced security and functionality. 
		''' </summary>
		Public Sub New(ByVal appId As String, ByVal secret As String, ByVal securityAlgorithm As String, ByVal forceDelAuthNonProvisioned As Boolean)
			Me.New(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, Nothing)
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' application ID, secret key, security algorithm,    
		''' forceDelAuthNonProvisioned and policy URL use.
		'''
		''' We recommend that you employ strong measures to protect
		''' the secret key. The secret key should never be
		''' exposed to the Web or other users.
		''' 
		''' The 'force_delauth_nonprovisioned' flag indicates whether
		''' your application is registered for Delegated Authentication 
		''' (that is, whether it uses an application ID and secret key). We 
		''' recommend that your Delegated Authentication application always 
		''' be registered for enhanced security and functionality. 
		''' </summary>
		Public Sub New(ByVal appId As String, ByVal secret As String, ByVal securityAlgorithm As String, ByVal forceDelAuthNonProvisioned As Boolean, ByVal policyUrl As String)
			Me.New(appId, secret, securityAlgorithm, forceDelAuthNonProvisioned, policyUrl, Nothing)
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module with the
		''' application ID, secret key, security algorithm,    
		''' forceDelAuthNonProvisioned, policy URL and return URL.
		'''
		''' We recommend that you employ strong measures to protect
		''' the secret key. The secret key should never be
		''' exposed to the Web or other users.
		''' 
		''' The 'force_delauth_nonprovisioned' flag indicates whether
		''' your application is registered for Delegated Authentication 
		''' (that is, whether it uses an application ID and secret key). We 
		''' recommend that your Delegated Authentication application always 
        ''' be registered for enhanced security and functionality.
        ''' </summary>
		Public Sub New(ByVal appId As String, ByVal secret As String, ByVal securityAlgorithm As String, ByVal forceDelAuthNonProvisioned As Boolean, ByVal policyUrl As String, ByVal returnUrl As String)
			Me.ForceDelAuthNonProvisioned = forceDelAuthNonProvisioned
			Me.AppId = appId
			Me.Secret = secret
			Me.SecurityAlgorithm = securityAlgorithm
			Me.PolicyUrl = policyUrl
			Me.ReturnUrl = returnUrl
		End Sub

		''' <summary>
		''' Initialize the WindowsLiveLogin module from the
		''' web.config file if loadAppSettings is true. Otherwise,
		''' you will have to manually set the AppId, Secret and
		''' SecurityAlgorithm properties.
		''' 
		''' In a Delegated Authentication scenario, you may also specify
		''' the return and privacy policy URLs to use, as shown in the 
		''' Delegated Authentication samples.     
		''' </summary>
		Public Sub New(ByVal loadAppSettings As Boolean)
			If (Not loadAppSettings) Then
				Return
			End If

			Dim appSettings As NameValueCollection = WebConfigurationManager.AppSettings
			If appSettings Is Nothing Then
				Throw New IOException("Error: WindowsLiveLogin: Failed to load the Web application settings.")
			End If

			Dim forceDelAuthNonProvisioned As String = appSettings("wll_force_delauth_nonprovisioned")

			If (Not String.IsNullOrEmpty(forceDelAuthNonProvisioned)) AndAlso (forceDelAuthNonProvisioned.ToLower() = "true") Then
				Me.ForceDelAuthNonProvisioned = True
			Else
				Me.ForceDelAuthNonProvisioned = False
			End If

			AppId = appSettings("wll_appid")
			Secret = appSettings("wll_secret")
			OldSecret = appSettings("wll_oldsecret")
			OldSecretExpiry = appSettings("wll_oldsecretexpiry")
			SecurityAlgorithm = appSettings("wll_securityalgorithm")
			PolicyUrl = appSettings("wll_policyurl")
			ReturnUrl = appSettings("wll_returnurl")
			BaseUrl = appSettings("wll_baseurl")
			SecureUrl = appSettings("wll_secureurl")
			ConsentUrl = appSettings("wll_consenturl")
		End Sub

		''' <summary><![CDATA[
		''' Initialize the WindowsLiveLogin module from a settings file. 
		''' 
		''' 'settingsFile' specifies the location of the XML settings
		''' file containing the application ID, secret key, an optional
		''' security algorithm and a privacy policy URL (required for
		''' Delegated Auth).  The file is of the following format:
		''' 
		''' <windowslivelogin>
		'''   <appid>APPID</appid>
		'''   <secret>SECRET</secret>
		'''   <securityalgorithm>wsignin1.0</securityalgorithm>
		'''   <policyurl>http://[your domain]/[your privacy policy]</policyurl>
		'''   <returnurl>http://[your domain]/[your return url]</policyurl>
		''' </windowslivelogin>
		''' 
		''' In a Delegated Authentication scenario, you may also specify
		''' 'returnurl' and 'policyurl' in the settings file. 
		'''  
		''' We recommend that you store the Windows Live Login settings file
		''' in an area on your server that cannot be accessed through
		''' the Internet. This file contains important confidential
		''' information.      
		''' ]]></summary>
		Public Sub New(ByVal settingsFile As String)
			Dim settings As NameValueCollection = parseSettings(settingsFile)

			Dim forceDelAuthNonProvisioned As String = settings("force_delauth_nonprovisioned")

			If (Not String.IsNullOrEmpty(forceDelAuthNonProvisioned)) AndAlso (forceDelAuthNonProvisioned.ToLower() = "true") Then
				Me.ForceDelAuthNonProvisioned = True
			Else
				Me.ForceDelAuthNonProvisioned = False
			End If

			AppId = settings("appid")
			Secret = settings("secret")
			OldSecret = settings("oldsecret")
			OldSecretExpiry = settings("oldsecretexpiry")
			SecurityAlgorithm = settings("securityalgorithm")
			PolicyUrl = settings("policyurl")
			ReturnUrl = settings("returnurl")
			BaseUrl = settings("baseurl")
			SecureUrl = settings("secureurl")
			ConsentUrl = settings("consenturl")
		End Sub

        Private applicationId As String

        ''' <summary>
        ''' Gets or sets the application ID.
        ''' </summary>
        Public Property AppId() As String
            Set(ByVal value As String)
                If String.IsNullOrEmpty(value) Then
                    If ForceDelAuthNonProvisioned Then
                        Return
                    End If

                    Throw New ArgumentNullException("value")
                End If

                Dim re As New Regex("^\w+$")
                If (Not re.IsMatch(value)) Then
                    Throw New ArgumentException("Error: AppId: Application ID must be alphanumeric: " & value)
                End If

                applicationId = value
            End Set

            Get
                If String.IsNullOrEmpty(applicationId) Then
                    Throw New InvalidOperationException("Error: AppId: Application ID was not set. Aborting.")
                End If

                Return applicationId
            End Get
        End Property

		Private cryptKey() As Byte
		Private signKey() As Byte

		''' <summary>
		''' Sets your secret key. Use this method if you did not specify 
		''' a secret key at initialization.
		''' </summary>
		Public Property Secret() As String
			Set(ByVal value As String)
				If String.IsNullOrEmpty(value) Then
					If ForceDelAuthNonProvisioned Then
						Return
					End If

					Throw New ArgumentNullException("value")
				End If

				If value.Length < 16 Then
					Throw New ArgumentException("Error: Secret: Secret key is expected to be longer than 16 characters: " & value.Length)
				End If

				cryptKey = derive(value, "ENCRYPTION")
				signKey = derive(value, "SIGNATURE")
			End Set

			Get
				Return Nothing
			End Get
		End Property

		Private oldCryptKey() As Byte
		Private oldSignKey() As Byte

		''' <summary>
		''' Sets your old secret key.
		''' 
		''' Use this property to set your old secret key if you are in the
		''' process of transitioning to a new secret key. You may need this 
		''' property because the Windows Live ID servers can take up to 
		''' 24 hours to propagate a new secret key after you have updated 
		''' your application settings.
		''' 
		''' If an old secret key is specified here and has not expired
		''' (as determined by the OldSecretExpiry setting), it will be used
		''' as a fallback if token decryption fails with the new secret 
		''' key.
		''' </summary>
		Public Property OldSecret() As String
			Set(ByVal value As String)
				If String.IsNullOrEmpty(value) Then
					Return
				End If

				If value.Length < 16 Then
					Throw New ArgumentException("Error: OldSecret: Secret key is expected to be longer than 16 characters: " & value.Length)
				End If

				oldCryptKey = derive(value, "ENCRYPTION")
				oldSignKey = derive(value, "SIGNATURE")
			End Set

			Get
				Return Nothing
			End Get
		End Property

		Private oldSecretExpiryString As String
        Private oldSecretExpiryDate As DateTime

        ''' <summary>
        ''' Sets or gets the expiry time for your old secret key.
        ''' 
        ''' After this time has passed, the old secret key will no longer be
        ''' used even if token decryption fails with the new secret key.
        '''
        ''' The old secret expiry time is represented as the number of seconds
        ''' elapsed since January 1, 1970. 
        ''' </summary>
        Public Property OldSecretExpiry() As String
            Set(ByVal value As String)
                If String.IsNullOrEmpty(value) Then
                    Return
                End If

                oldSecretExpiryString = value
                Dim timestampInt As Integer

                Try
                    timestampInt = Convert.ToInt32(value)
                Catch e1 As Exception
                    Throw New ArgumentException("Error: OldSecretExpiry: Invalid timestamp: " & value)
                End Try

                Dim refTime As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                oldSecretExpiryDate = refTime.AddSeconds(timestampInt)
            End Set

            Get
                Return oldSecretExpiryString
            End Get
        End Property

        Private securityAlgorithmVersion As String

        ''' <summary>
        ''' Sets or gets the version of the security algorithm being used.
        ''' </summary>
        Public Property SecurityAlgorithm() As String
            Set(ByVal value As String)
                securityAlgorithmVersion = value
            End Set

            Get
                If String.IsNullOrEmpty(securityAlgorithmVersion) Then
                    Return "wsignin1.0"
                End If

                Return securityAlgorithmVersion
            End Get
        End Property

        Private forceDelegatedAuthNonProvisioned As Boolean = False

        ''' <summary>
        ''' Sets or gets a flag that indicates whether Delegated Authentication
        ''' is non-provisioned (i.e. does not use an application ID or secret
        ''' key).
        ''' </summary>
        Public Property ForceDelAuthNonProvisioned() As Boolean
            Set(ByVal value As Boolean)
                forceDelegatedAuthNonProvisioned = value
            End Set

            Get
                Return forceDelegatedAuthNonProvisioned
            End Get
        End Property

        Private policyUrlDelAuth As String

        ''' <summary>
        ''' Sets or gets the privacy policy URL.
        ''' 
        ''' Set the property for Delegated Authentication, if you did 
        ''' not provide one at initialization time.
        ''' </summary>
        Public Property PolicyUrl() As String
            Set(ByVal value As String)
                If String.IsNullOrEmpty(value) AndAlso ForceDelAuthNonProvisioned Then
                    Throw New ArgumentNullException("value")
                End If

                policyUrlDelAuth = value
            End Set

            Get
                If String.IsNullOrEmpty(policyUrlDelAuth) Then
                    debug("Warning: In the initial release of Delegated Auth, a Policy URL must be configured in the SDK for both provisioned and non-provisioned scenarios.")

                    If ForceDelAuthNonProvisioned Then
                        Throw New InvalidOperationException("Error: PolicyUrl: Policy URL must be set in a Delegated Auth non-provisioned scenario. Aborting.")
                    End If
                End If

                Return policyUrlDelAuth
            End Get
        End Property

        Private returnUrlDelAuth As String

        ''' <summary>
        ''' Sets or gets the return URL--the URL on your site to which the consent 
        ''' service redirects users (along with the action, consent token, 
        ''' and application context) after they have successfully provided 
        ''' consent information for Delegated Authentication. 
        ''' 
        ''' This value will override the return URL specified during 
        ''' registration.
        ''' </summary>
        Public Property ReturnUrl() As String
            Set(ByVal value As String)
                If String.IsNullOrEmpty(value) AndAlso ForceDelAuthNonProvisioned Then
                    Throw New ArgumentNullException("value")
                End If

                returnUrlDelAuth = value
            End Set

            Get
                If String.IsNullOrEmpty(returnUrlDelAuth) AndAlso ForceDelAuthNonProvisioned Then
                    Throw New InvalidOperationException("Error: ReturnUrl: Return URL must be specified in a delegated auth non-provisioned scenario. Aborting.")
                End If

                Return returnUrlDelAuth
            End Get
        End Property

        Private baseWindowsLiveLoginUrl As String

        ''' <summary>
        ''' Sets or gets the URL to use for the Windows Live Login server. 
        ''' You should not have to use or change this. Furthermore, we
        ''' recommend that you use the Sign In control instead of
        ''' the URL methods provided here.
        ''' </summary>
        Public Property BaseUrl() As String
            Set(ByVal value As String)
                baseWindowsLiveLoginUrl = value
            End Set

            Get
                If String.IsNullOrEmpty(baseWindowsLiveLoginUrl) Then
                    Return "http://login.live.com/"
                End If

                Return baseWindowsLiveLoginUrl
            End Get
        End Property

        Private secureWindowsLiveLoginUrl As String

        ''' <summary>
        ''' Sets or gets the secure (HTTPS) URL to use for the Windows Live
        ''' Login server.  You should not have to use or change this
        ''' directly.  
        ''' </summary>
        Public Property SecureUrl() As String
            Set(ByVal value As String)
                secureWindowsLiveLoginUrl = value
            End Set

            Get
                If String.IsNullOrEmpty(secureWindowsLiveLoginUrl) Then
                    Return "https://login.live.com/"
                End If

                Return secureWindowsLiveLoginUrl
            End Get
        End Property

        Private consentServerUrl As String

        ''' <summary>
        ''' Sets or gets the URL to use for the Windows Live Consent server. You
        ''' should not have to use or change this directly.
        ''' </summary>
        Public Property ConsentUrl() As String
            Set(ByVal value As String)
                consentServerUrl = value
            End Set

            Get
                If String.IsNullOrEmpty(consentServerUrl) Then
                    Return "https://consent.live.com/"
                End If

                Return consentServerUrl
            End Get
        End Property

        ' Methods for Web Authentication support. 

        ''' <summary>
        ''' Returns the sign-in URL to use for the Windows Live Login server.
        ''' We recommend that you use the Sign In control instead.
        ''' </summary>
        ''' <returns>Sign-in URL</returns>
        Public Function GetLoginUrl() As String
            Return GetLoginUrl(Nothing)
        End Function

        ''' <summary>
        ''' Returns the sign-in URL to use for the Windows Live Login server.
        ''' We recommend that you use the Sign In control instead.
        ''' </summary>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the sign-in
        ''' response for site-specific use.</param>
        ''' <returns>Sign-in URL</returns>
        Public Function GetLoginUrl(ByVal context As String) As String
            Return GetLoginUrl(context, Nothing)
        End Function

        ''' <summary>
        ''' Returns the sign-in URL to use for the Windows Live Login server.
        ''' We recommend that you use the Sign In control instead.
        ''' </summary>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the sign-in
        ''' response for site-specific use.</param>
        ''' <param name="market">The language in which the sign-in page is 
        ''' displayed is configured by culture ID (For example, 'fr-fr' or 
        ''' 'en-us') specified in the 'market' parameter.</param>
        ''' <returns>Sign-in URL</returns>
        Public Function GetLoginUrl(ByVal context As String, ByVal market As String) As String
            Dim alg As String = "&alg=" & SecurityAlgorithm

            context = IIf(String.IsNullOrEmpty(context), String.Empty, "&appctx=" + HttpUtility.UrlEncode(context))

            market = IIf(String.IsNullOrEmpty(market), String.Empty, "&mkt=" + HttpUtility.UrlEncode(market))

            Return BaseUrl & "wlogin.srf?appid=" & AppId & alg & context & market
        End Function

        ''' <summary>
        ''' Returns the sign-out URL to use for the Windows Live Login server.
        ''' We recommend that you use the Sign In control instead.
        ''' </summary>
        ''' <returns>Sign-out URL</returns>
        Public Function GetLogoutUrl() As String
            Return GetLogoutUrl(Nothing)
        End Function

        ''' <summary>
        ''' Returns the sign-out URL to use for the Windows Live Login server.
        ''' We recommend that you use the Sign In control instead.
        ''' </summary>
        ''' <param name="market">The language in which the sign-in page is 
        ''' displayed is configured by culture ID (For example, 'fr-fr' or 
        ''' 'en-us') specified in the 'market' parameter.</param>
        ''' <returns>Sign-out URL</returns>
        Public Function GetLogoutUrl(ByVal market As String) As String
            market = IIf(String.IsNullOrEmpty(market), String.Empty, "&mkt=" + HttpUtility.UrlEncode(market))

            Return BaseUrl & "logout.srf?appid=" & AppId & market
        End Function

        ''' <summary>
        ''' Holds the user information after a successful sign-in.
        ''' </summary>
        Public Class User
            Public Sub New(ByVal timestamp As String, ByVal id As String, ByVal flags As String, ByVal context As String, ByVal token As String)
                setTimestamp(timestamp)
                setId(id)
                setFlags(flags)
                setContext(context)
                setToken(token)
            End Sub

            Private ssoTokenTimestamp As DateTime

            ''' <summary>
            '''  Returns the timestamp as obtained from the SSO token.
            ''' </summary>
            Public ReadOnly Property Timestamp() As DateTime
                Get
                    Return ssoTokenTimestamp
                End Get
            End Property

            ''' <summary>
            ''' Sets the Unix timestamp.
            ''' </summary>
            ''' <param name="timestamp"></param>
            Private Sub setTimestamp(ByVal timestamp As String)
                If String.IsNullOrEmpty(timestamp) Then
                    Throw New ArgumentException("Error: User: Null timestamp in token.")
                End If

                Dim timestampInt As Integer

                Try
                    timestampInt = Convert.ToInt32(timestamp)
                Catch e1 As Exception
                    Throw New ArgumentException("Error: User: Invalid timestamp: " & timestamp)
                End Try

                Dim refTime As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                Me.ssoTokenTimestamp = refTime.AddSeconds(timestampInt)
            End Sub

            Private uniqueId As String

            ''' <summary>
            ''' Returns the pairwise unique ID for the user.
            ''' </summary>
            Public ReadOnly Property Id() As String
                Get
                    Return uniqueId
                End Get
            End Property

            ''' <summary>
            ''' Sets the pairwise unique ID for the user.
            ''' </summary>
            ''' <param name="id">User id</param>
            Private Sub setId(ByVal id As String)
                If String.IsNullOrEmpty(id) Then
                    Throw New ArgumentException("Error: User: Null id in token.")
                End If

                Dim re As New Regex("^\w+$")
                If (Not re.IsMatch(id)) Then
                    Throw New ArgumentException("Error: User: Invalid id: " & id)
                End If

                Me.uniqueId = id
            End Sub

            Private usePersistentCookieForUser As Boolean

            ''' <summary>
            ''' Indicates whether the application
            ''' is expected to store the user token in a session or
            ''' persistent cookie.
            ''' </summary>
            Public ReadOnly Property UsePersistentCookie() As Boolean
                Get
                    Return usePersistentCookieForUser
                End Get
            End Property

            ''' <summary>
            ''' Sets the usePersistentCookie flag for the user.
            ''' </summary>
            ''' <param name="flags"></param>
            Private Sub setFlags(ByVal flags As String)
                Me.usePersistentCookieForUser = False

                If (Not String.IsNullOrEmpty(flags)) Then
                    Try
                        Dim flagsInt As Integer = Convert.ToInt32(flags)
                        Me.usePersistentCookieForUser = ((flagsInt Mod 2) = 1)
                    Catch e1 As Exception
                        Throw New ArgumentException("Error: User: Invalid flags: " & flags)
                    End Try
                End If
            End Sub

            Private appContext As String

            ''' <summary>
            ''' Returns the application context that was originally passed
            ''' to the sign-in request, if any.
            ''' </summary>
            Public ReadOnly Property Context() As String
                Get
                    Return appContext
                End Get
            End Property

            ''' <summary>
            ''' Sets the the Application context.
            ''' </summary>
            ''' <param name="context"></param>
            Private Sub setContext(ByVal context As String)
                Me.appContext = context
            End Sub

            Private webAuthToken As String

            ''' <summary>
            ''' Returns the encrypted Web Authentication token containing 
            ''' the UID. This can be cached in a cookie and the UID can be
            ''' retrieved by calling the ProcessToken method.
            ''' </summary>
            Public ReadOnly Property Token() As String
                Get
                    Return webAuthToken
                End Get
            End Property

            ''' <summary>
            ''' Sets the the User token.
            ''' </summary>
            ''' <param name="token"></param>
            Private Sub setToken(ByVal token As String)
                Me.webAuthToken = token
            End Sub
        End Class

        ''' <summary>
        ''' Processes the sign-in response from the Windows Live Login server.
        ''' </summary>
        '''
        ''' <param name="query">Contains the preprocessed POST query
        ''' such as that returned by HttpRequest.Form</param>
        ''' 
        ''' <returns>The method returns a User object on successful
        ''' sign-in; otherwise null.</returns>
        Public Function ProcessLogin(ByVal query As NameValueCollection) As User
            If query Is Nothing Then
                debug("Error: ProcessLogin: Invalid query.")
                Return Nothing
            End If

            Dim action As String = query("action")

            If action <> "login" Then
                debug("Warning: ProcessLogin: query action ignored: " & action)
                Return Nothing
            End If

            Dim token As String = query("stoken")
            Dim context As String = query("appctx")

            If context IsNot Nothing Then
                context = HttpUtility.UrlDecode(context)
            End If

            Return ProcessToken(token, context)
        End Function

        ''' <summary>
        ''' Decodes and validates a Web Authentication token. Returns a User
        ''' object on success.
        ''' </summary>
        Public Function ProcessToken(ByVal token As String) As User
            Return ProcessToken(token, Nothing)
        End Function

        ''' <summary>
        ''' Decodes and validates a Web Authentication token. Returns a User
        ''' object on success. If a context is passed in, it will be
        ''' returned as the context field in the User object.
        ''' </summary>
        ''' <param name="token">Web Authentication token</param>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the sign-in
        ''' response for site-specific use.</param>        
        ''' <returns>User object</returns>
        Public Function ProcessToken(ByVal token As String, ByVal context As String) As User
            If String.IsNullOrEmpty(token) Then
                debug("Error: ProcessToken: Invalid token.")
                Return Nothing
            End If

            Dim stoken As String = DecodeAndValidateToken(token)

            If String.IsNullOrEmpty(stoken) Then
                debug("Error: ProcessToken: Failed to decode/validate token: " & token)
                Return Nothing
            End If

            Dim parsedToken As NameValueCollection = parse(stoken)
            If parsedToken Is Nothing OrElse parsedToken.Count < 3 Then
                debug("Error: ProcessToken: Failed to parse token after decoding: " & token)
                Return Nothing
            End If

            Dim appId As String = parsedToken("appid")
            If appId <> Me.AppId Then
                debug("Error: ProcessToken: Application ID in token did not match ours: " & appId & ", " & appId)
                Return Nothing
            End If

            Dim user As User = Nothing
            Try
                user = New User(parsedToken("ts"), parsedToken("uid"), parsedToken("flags"), context, token)
            Catch e As Exception
                debug("Error: ProcessToken: Contents of token considered invalid: " & e.ToString())
            End Try
            Return user
        End Function

        ''' <summary>
        ''' Returns an appropriate content type and body
        ''' response that the application handler can return to
        ''' signify a successful sign-out from the application.
        ''' 
        ''' When a user signs out of Windows Live or a Windows Live
        ''' application, a best-effort attempt is made to sign the user out
        ''' from all other Windows Live applications the user might be signed
        ''' in to. This is done by calling the handler page for each
        ''' application with 'action' parameter set to 'clearcookie' in the query
        ''' string. The application handler is then responsible for clearing
        ''' any cookies or data associated with the sign-in. After successfully
        ''' signing the user out, the handler should return a GIF (any
        ''' GIF) as response to the action=clearcookie query.
        ''' </summary>
        Public Sub GetClearCookieResponse(<System.Runtime.InteropServices.Out()> ByRef type As String, <System.Runtime.InteropServices.Out()> ByRef content() As Byte)
            Const gif As String = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7"
            type = "image/gif"
            content = Convert.FromBase64String(gif)
        End Sub

        ' Methods for Delegated Authentication support. 

        ''' <summary>
        ''' Returns the consent URL to use for Delegated Authentication for
        ''' the given comma-delimited list of offers.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <returns>Consent URL</returns>
        Public Function GetConsentUrl(ByVal offers As String) As String
            Return GetConsentUrl(offers, Nothing)
        End Function

        ''' <summary>
        ''' Returns the consent URL to use for Delegated Authentication for
        ''' the given comma-delimited list of offers.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the consent 
        ''' response for site-specific use.</param>
        ''' <returns>Consent URL</returns>
        Public Function GetConsentUrl(ByVal offers As String, ByVal context As String) As String
            Return GetConsentUrl(offers, context, Nothing)
        End Function

        ''' <summary>
        ''' Returns the consent URL to use for Delegated Authentication for
        ''' the given comma-delimited list of offers.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the consent 
        ''' response for site-specific use.</param>
        ''' <param name="ru">The registered/configured return URL will be 
        ''' overridden by 'ru' specified here.</param>
        ''' <returns>Consent URL</returns>
        Public Function GetConsentUrl(ByVal offers As String, ByVal context As String, ByVal ru As String) As String
            Return GetConsentUrl(offers, context, ru, Nothing)
        End Function

        ''' <summary>
        ''' Returns the consent URL to use for Delegated Authentication for
        ''' the given comma-delimited list of offers.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the sign-in
        ''' response for site-specific use.</param>
        ''' <param name="ru">The registered/configured return URL will be 
        ''' overridden by 'ru' specified here.</param>
        ''' <param name="market">The language in which the consent page is 
        ''' displayed is configured by culture ID (For example, 'fr-fr' or 
        ''' 'en-us') specified in the 'market' parameter.</param>
        ''' <returns>Consent URL</returns>
        Public Function GetConsentUrl(ByVal offers As String, ByVal context As String, ByVal ru As String, ByVal market As String) As String
            If String.IsNullOrEmpty(offers) Then
                Throw New ArgumentException("Error: GetConsentUrl: Invalid offers list.")
            End If

            offers = "?ps=" & HttpUtility.UrlEncode(offers)

            context = IIf(String.IsNullOrEmpty(context), String.Empty, "&appctx=" + HttpUtility.UrlEncode(context))

            If String.IsNullOrEmpty(ru) Then
                ru = ReturnUrl
            End If

            ru = IIf(String.IsNullOrEmpty(ru), String.Empty, "&ru=" + HttpUtility.UrlEncode(ru))

            market = IIf(String.IsNullOrEmpty(market), String.Empty, "&mkt=" + HttpUtility.UrlEncode(market))

            Dim pu As String = String.Empty

            If (Not String.IsNullOrEmpty(PolicyUrl)) Then
                pu = "&pl=" & HttpUtility.UrlEncode(PolicyUrl)
            End If

            Dim app As String = String.Empty

            If (Not ForceDelAuthNonProvisioned) Then
                app = "&app=" & GetAppVerifier()
            End If

            Return (ConsentUrl & "Delegation.aspx" & offers & context & ru & pu & market & app)
        End Function

        ''' <summary>
        ''' Returns the URL to use to download a new consent token, given the 
        ''' offers and refresh token.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="refreshToken">Refresh token.</param>
        ''' <returns>Refresh consent token URL</returns>
        Public Function GetRefreshConsentTokenUrl(ByVal offers As String, ByVal refreshToken As String) As String
            Return GetRefreshConsentTokenUrl(offers, refreshToken, Nothing)
        End Function

        ''' <summary>
        ''' Returns the URL to use to download a new consent token, given the 
        ''' offers and refresh token.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="refreshToken">Refresh token.</param>
        ''' <param name="ru">The registered/configured return URL will be 
        ''' overridden by 'ru' specified here.</param>
        ''' <returns>Refresh consent token URL</returns>
        Public Function GetRefreshConsentTokenUrl(ByVal offers As String, ByVal refreshToken As String, ByVal ru As String) As String
            If String.IsNullOrEmpty(offers) Then
                Throw New ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid offers list.")
            End If

            offers = "?ps=" & HttpUtility.UrlEncode(offers)

            If String.IsNullOrEmpty(refreshToken) Then
                Throw New ArgumentException("Error: GetRefreshConsentTokenUrl: Invalid refresh token.")
            End If

            refreshToken = "&reft=" & refreshToken

            If String.IsNullOrEmpty(ru) Then
                ru = ReturnUrl
            End If

            ru = IIf(String.IsNullOrEmpty(ru), String.Empty, "&ru=" + HttpUtility.UrlEncode(ru))

            Dim app As String = String.Empty

            If (Not ForceDelAuthNonProvisioned) Then
                app = "&app=" & GetAppVerifier()
            End If

            Return ConsentUrl & "RefreshToken.aspx" & offers & refreshToken & ru & app
        End Function

        ''' <summary>
        ''' Returns the URL for the consent-management user interface.
        ''' </summary>
        ''' <returns>Manage consent URL</returns>
        Public Function GetManageConsentUrl() As String
            Return GetManageConsentUrl(Nothing)
        End Function

        ''' <summary>
        ''' Returns the URL for the consent-management user interface.
        ''' </summary>
        ''' <param name="market">The language in which the consent page is 
        ''' displayed is configured by culture ID (For example, 'fr-fr' or 
        ''' 'en-us') specified in the 'market' parameter.</param>
        ''' <returns>Manage consent URL</returns>
        Public Function GetManageConsentUrl(ByVal market As String) As String
            market = IIf(String.IsNullOrEmpty(market), String.Empty, "?mkt=" + HttpUtility.UrlEncode(market))

            Return ConsentUrl & "ManageConsent.aspx" & market
        End Function

        ''' <summary>
        ''' Holds the Consent Token object corresponding to consent granted. 
        ''' </summary>
        Public Class ConsentToken
            Private wll As WindowsLiveLogin

            ''' <summary>
            ''' Initialize the ConsentToken.
            ''' </summary>
            ''' <param name="wll">WindowsLiveLogin</param>
            ''' <param name="delegationToken">Delegation token</param>
            ''' <param name="refreshToken">Refresh token</param>
            ''' <param name="sessionKey">Session key</param>
            ''' <param name="expiry">Expiry</param>
            ''' <param name="offers">Offers</param>
            ''' <param name="locationID">Location ID</param>
            ''' <param name="context">Application context</param>
            ''' <param name="decodedToken">Decoded token</param>
            ''' <param name="token">Raw token</param>
            Public Sub New(ByVal wll As WindowsLiveLogin, ByVal delegationToken As String, ByVal refreshToken As String, ByVal sessionKey As String, ByVal expiry As String, ByVal offers As String, ByVal locationID As String, ByVal context As String, ByVal decodedToken As String, ByVal token As String)
                Me.wll = wll
                setDelegationToken(delegationToken)
                setRefreshToken(refreshToken)
                setSessionKey(sessionKey)
                setExpiry(expiry)
                setOffers(offers)
                setLocationID(locationID)
                setContext(context)
                setDecodedToken(decodedToken)
                setToken(token)
            End Sub

            Private delToken As String

            ''' <summary>
            ''' Gets the Delegation token.
            ''' </summary>
            Public ReadOnly Property DelegationToken() As String
                Get
                    Return delToken
                End Get
            End Property

            ''' <summary>
            ''' Sets the Delegation token.
            ''' </summary>
            ''' <param name="delegationToken">Delegation token</param>
            Private Sub setDelegationToken(ByVal delegationToken As String)
                If String.IsNullOrEmpty(delegationToken) Then
                    Throw New ArgumentException("Error: ConsentToken: Null delegation token.")
                End If

                Me.delToken = delegationToken
            End Sub

            Private refreshConsentToken As String

            ''' <summary>
            ''' Gets the refresh token.
            ''' </summary>
            Public ReadOnly Property RefreshToken() As String
                Get
                    Return refreshConsentToken
                End Get
            End Property

            ''' <summary>
            ''' Sets the refresh token.
            ''' </summary>
            ''' <param name="refreshToken">Refresh token</param>
            Private Sub setRefreshToken(ByVal refreshToken As String)
                Me.refreshConsentToken = refreshToken
            End Sub

            Private consentTokenSessionKey() As Byte

            ''' <summary>
            ''' Gets the session key.
            ''' </summary>
            Public ReadOnly Property SessionKey() As Byte()
                Get
                    Return consentTokenSessionKey
                End Get
            End Property

            ''' <summary>
            ''' Sets the session key.
            ''' </summary>
            ''' <param name="sessionKey">Session key</param>
            Private Sub setSessionKey(ByVal sessionKey As String)
                If String.IsNullOrEmpty(sessionKey) Then
                    Throw New ArgumentException("Error: ConsentToken: Null session key.")
                End If

                Me.consentTokenSessionKey = WindowsLiveLogin.u64(sessionKey)
            End Sub

            Private expiryTime As DateTime

            ''' <summary>
            ''' Gets the expiry time of delegation token.
            ''' </summary>
            Public ReadOnly Property Expiry() As DateTime
                Get
                    Return expiryTime
                End Get
            End Property

            ''' <summary>
            ''' Sets the expiry time of delegation token.
            ''' </summary>
            ''' <param name="expiry">Expiry time</param>
            Private Sub setExpiry(ByVal expiry As String)
                If String.IsNullOrEmpty(expiry) Then
                    Throw New ArgumentException("Error: ConsentToken: Null expiry time.")
                End If

                Dim expiryInt As Integer

                Try
                    expiryInt = Convert.ToInt32(expiry)
                Catch e1 As Exception
                    Throw New ArgumentException("Error: Consent: Invalid expiry time: " & expiry)
                End Try

                Dim refTime As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                Me.expiryTime = refTime.AddSeconds(expiryInt)
            End Sub

            Private consentOffers As IList

            ''' <summary>
            ''' Gets the list of offers/actions for which the user granted consent.
            ''' </summary>
            Public ReadOnly Property Offers() As IList
                Get
                    Return consentOffers
                End Get
            End Property

            Private consentOffersString As String

            ''' <summary>
            ''' Gets the string representation of all the offers/actions for which 
            ''' the user granted consent.
            ''' </summary>
            Public ReadOnly Property OffersString() As String
                Get
                    Return consentOffersString
                End Get
            End Property

            ''' <summary>
            ''' Sets the offers/actions for which user granted consent.
            ''' </summary>
            ''' <param name="offers">Comma-delimited list of offers</param>
            Private Sub setOffers(ByVal offers As String)
                If String.IsNullOrEmpty(offers) Then
                    Throw New ArgumentException("Error: ConsentToken: Null offers.")
                End If

                offers = HttpUtility.UrlDecode(offers)

                Me.consentOffersString = String.Empty
                Me.consentOffers = New ArrayList()

                Dim offersList() As String = offers.Split(New Char() {";"c})

                For Each offer As String In offersList
                    If Not (Me.consentOffersString = String.Empty) Then
                        Me.consentOffersString &= ","
                    End If

                    Dim separator As Integer = offer.IndexOf(":"c)
                    If separator = -1 Then
                        debug("Warning: ConsentToken: offer may be invalid: " & offer)
                        Me.consentOffers.Add(offer)
                        Me.consentOffersString &= offer
                    Else
                        Dim o As String = offer.Substring(0, separator)
                        Me.consentOffers.Add(o)
                        Me.consentOffersString &= o
                    End If
                Next offer
            End Sub

            Private consentLocationID As String

            ''' <summary>
            ''' Gets the location ID.
            ''' </summary>
            Public ReadOnly Property LocationID() As String
                Get
                    Return consentLocationID
                End Get
            End Property

            ''' <summary>
            ''' Sets the location ID.
            ''' </summary>
            ''' <param name="locationID">Location ID</param>
            Private Sub setLocationID(ByVal locationID As String)
                Me.consentLocationID = locationID
            End Sub

            Private appContext As String

            ''' <summary>
            ''' Returns the application context that was originally passed 
            ''' to the consent request, if any.
            ''' </summary>
            Public ReadOnly Property Context() As String
                Get
                    Return appContext
                End Get
            End Property

            ''' <summary>
            ''' Sets the application context.
            ''' </summary>
            ''' <param name="context">Application context</param>
            Private Sub setContext(ByVal context As String)
                Me.appContext = context
            End Sub

            Private decodedConsentToken As String

            ''' <summary>
            ''' Gets the decoded token.
            ''' </summary>
            Public ReadOnly Property DecodedToken() As String
                Get
                    Return decodedConsentToken
                End Get
            End Property

            ''' <summary>
            ''' Sets the decoded token.
            ''' </summary>
            ''' <param name="decodedToken">Decoded token</param>
            Private Sub setDecodedToken(ByVal decodedToken As String)
                Me.decodedConsentToken = decodedToken
            End Sub

            Private rawToken As String

            ''' <summary>
            ''' Gets the raw token.
            ''' </summary>
            Public ReadOnly Property Token() As String
                Get
                    Return rawToken
                End Get
            End Property

            ''' <summary>
            ''' Sets the raw token.
            ''' </summary>
            ''' <param name="token">Raw token</param>
            Private Sub setToken(ByVal token As String)
                Me.rawToken = token
            End Sub

            ''' <summary>
            ''' Indicates whether the delegation token is set and has not expired.
            ''' </summary>
            ''' <returns></returns>
            Public Function IsValid() As Boolean
                If String.IsNullOrEmpty(DelegationToken) Then
                    Return False
                End If

                If DateTime.UtcNow.AddSeconds(-300) > Expiry Then
                    Return False
                End If

                Return True
            End Function

            ''' <summary>
            ''' Attempt to refresh the current token and replace it. If operation succeeds 
            ''' true is returned to signify success.
            ''' </summary>
            ''' <returns></returns>
            Public Function Refresh() As Boolean
                Dim ct As ConsentToken = wll.RefreshConsentToken(Me)

                If ct Is Nothing Then
                    Return False
                End If

                copy(ct)

                Return True
            End Function

            ''' <summary>
            ''' Makes a copy of the ConsentToken object.
            ''' </summary>
            ''' <param name="consentToken"></param>
            Private Sub copy(ByVal consentToken As ConsentToken)
                Me.delToken = consentToken.delegationToken
                Me.refreshConsentToken = consentToken.refreshToken
                Me.consentTokenSessionKey = consentToken.sessionKey
                Me.expiryTime = consentToken.expiry
                Me.consentOffers = consentToken.offers
                Me.consentLocationID = consentToken.locationID
                Me.consentOffersString = consentToken.offersString
                Me.decodedConsentToken = consentToken.decodedToken
                Me.rawToken = consentToken.token
            End Sub
        End Class

        ''' <summary>
        ''' Processes the POST response from the Delegated Authentication 
        ''' service after a user has granted consent. The processConsent
        ''' function extracts the consent token string and returns the result 
        ''' of invoking the processConsentToken method. 
        ''' </summary>
        ''' <param name="query">Response from the Delegated Authentication service.</param>
        ''' <returns>ConsentToken</returns>
        Public Function ProcessConsent(ByVal query As NameValueCollection) As ConsentToken
            If query Is Nothing Then
                debug("Error: ProcessConsent: Invalid query.")
                Return Nothing
            End If

            Dim action As String = query("action")

            If action <> "delauth" Then
                debug("Warning: ProcessConsent: query action ignored: " & action)
                Return Nothing
            End If

            If query("ResponseCode") <> "RequestApproved" Then
                debug("Error: ProcessConsent: Consent was not successfully granted: " & query("ResponseCode"))
                Return Nothing
            End If

            Dim token As String = query("ConsentToken")
            Dim context As String = query("appctx")

            If (Not String.IsNullOrEmpty(context)) Then
                context = HttpUtility.UrlDecode(context)
            End If

            Return ProcessConsentToken(token, context)
        End Function

        ''' <summary>
        ''' Processes the consent token string that is returned in the POST 
        ''' response by the Delegated Authentication service after a 
        ''' user has granted consent.
        ''' </summary>
        ''' <param name="token">Raw token.</param>
        ''' <returns>ConsentToken</returns>
        Public Function ProcessConsentToken(ByVal token As String) As ConsentToken
            Return ProcessConsentToken(token, Nothing)
        End Function

        ''' <summary>
        ''' Processes the consent token string that is returned in the POST 
        ''' response by the Delegated Authentication service after a 
        ''' user has granted consent.
        ''' </summary>
        ''' <param name="token">Raw token.</param>
        ''' <param name="context">If you specify it, <paramref
        ''' name="context"/> will be returned as-is in the sign-in
        ''' response for site-specific use.</param>
        ''' <returns></returns>
        Public Function ProcessConsentToken(ByVal token As String, ByVal context As String) As ConsentToken
            Dim decodedToken As String = token

            If String.IsNullOrEmpty(token) Then
                debug("Error: ProcessConsentToken: Null token.")
                Return Nothing
            End If

            Dim parsedToken As NameValueCollection = parse(HttpUtility.UrlDecode(token))

            If (Not String.IsNullOrEmpty(parsedToken("eact"))) Then
                decodedToken = DecodeAndValidateToken(parsedToken("eact"))
                If String.IsNullOrEmpty(decodedToken) Then
                    debug("Error: ProcessConsentToken: Failed to decode/validate token: " & token)
                    Return Nothing
                End If

                parsedToken = parse(decodedToken)
                decodedToken = HttpUtility.UrlEncode(decodedToken)
            End If

            Dim consentToken As ConsentToken = Nothing
            Try
                consentToken = New ConsentToken(Me, parsedToken("delt"), parsedToken("reft"), parsedToken("skey"), parsedToken("exp"), parsedToken("offer"), parsedToken("lid"), context, decodedToken, token)
            Catch e As Exception
                debug("Error: ProcessConsentToken: Contents of token considered invalid: " & e.ToString())
            End Try

            Return consentToken
        End Function

        ''' <summary>
        ''' Attempts to obtain a new, refreshed token and return it. The 
        ''' original token is not modified.
        ''' </summary>
        ''' <param name="token">ConsentToken object.</param>
        ''' <returns>Refreshed ConsentToken object.</returns>
        Public Function RefreshConsentToken(ByVal token As ConsentToken) As ConsentToken
            Return RefreshConsentToken(token, Nothing)
        End Function

        ''' <summary>
        ''' Attempts to obtain a new, refreshed token and return it. The 
        ''' original token is not modified.
        ''' </summary>
        ''' <param name="token">ConsentToken object.</param>
        ''' <param name="ru">The registered/configured return URL will be 
        ''' overridden by 'ru' specified here.</param>
        ''' <returns>Refreshed ConsentToken object.</returns>
        Public Function RefreshConsentToken(ByVal token As ConsentToken, ByVal ru As String) As ConsentToken
            If token Is Nothing Then
                debug("Error: RefreshConsentToken: Null consent token.")
                Return Nothing
            End If

            Return RefreshConsentToken(token.OffersString, token.RefreshToken, ru)
        End Function

        ''' <summary>
        ''' Attempts to obtain a new, refreshed token and return it using 
        ''' the offers and refresh token. The original token is not modified.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="refreshToken">Refresh token.</param>
        ''' <returns>Refreshed ConsentToken object.</returns>
        Public Function RefreshConsentToken(ByVal offers As String, ByVal refreshToken As String) As ConsentToken
            Return RefreshConsentToken(offers, refreshToken, Nothing)
        End Function

        ''' <summary>
        ''' Attempts to obtain a new, refreshed token and return it using 
        ''' the offers and refresh token. The original token is not modified.
        ''' </summary>
        ''' <param name="offers">Comma-delimited list of offers.</param>
        ''' <param name="refreshToken">Refresh token.</param>
        ''' <param name="ru">The registered/configured return URL will be 
        ''' overridden by 'ru' specified here.</param>
        ''' <returns>Refreshed ConsentToken object.</returns>
        Public Function RefreshConsentToken(ByVal offers As String, ByVal refreshToken As String, ByVal ru As String) As ConsentToken
            Dim url As String = Nothing

            Try
                url = GetRefreshConsentTokenUrl(offers, refreshToken, ru)
            Catch e As Exception
                debug("Error: Failed to construct refresh consent token URL: " & e.ToString())
                Return Nothing
            End Try

            If String.IsNullOrEmpty(url) Then
                debug("Error: Failed to construct refresh consent token URL.")
                Return Nothing
            End If

            Dim body As String = fetch(url)

            If String.IsNullOrEmpty(body) Then
                debug("Error: RefreshConsentToken: Failed to download token.")
                Return Nothing
            End If

            Dim re As New Regex("{""ConsentToken"":""(.*)""}")
            Dim gc As GroupCollection = re.Match(body).Groups

            If gc.Count <> 2 Then
                debug("Error: RefreshConsentToken: Failed to extract token: " & body)
                Return Nothing
            End If

            Dim cc As CaptureCollection = gc(1).Captures

            If cc.Count <> 1 Then
                debug("Error: RefreshConsentToken: Failed to extract token: " & body)
                Return Nothing
            End If

            Return ProcessConsentToken(cc(0).ToString())
        End Function

        ' Common methods. 

        ''' <summary>
        ''' Decodes and validates the raw token.
        ''' </summary>
        ''' <param name="token"></param>
        ''' <returns></returns>
        Public Function DecodeAndValidateToken(ByVal token As String) As String
            Dim haveOldSecret As Boolean = False

            If (oldSecretExpiryDate <> Nothing) AndAlso (DateTime.UtcNow < oldSecretExpiryDate) Then
                If (oldCryptKey IsNot Nothing) AndAlso (oldSignKey IsNot Nothing) Then
                    haveOldSecret = True
                End If
            End If

            Dim stoken As String = DecodeAndValidateToken(token, cryptKey, signKey)

            If String.IsNullOrEmpty(stoken) Then
                If haveOldSecret Then
                    debug("Warning: Failed to validate token with current secret, attempting old secret.")
                    Return DecodeAndValidateToken(token, oldCryptKey, oldSignKey)
                End If
            End If

            Return stoken
        End Function

		''' <summary>
		''' Decodes and validates the raw token with appropriate crypt key 
		''' and sign key.
		''' </summary>
		''' <param name="token">Raw token.</param>
		''' <param name="cryptKey">Crypt key.</param>
		''' <param name="signKey">Sign key.</param>
		''' <returns></returns>
		Public Function DecodeAndValidateToken(ByVal token As String, ByVal cryptKey() As Byte, ByVal signKey() As Byte) As String
			Dim stoken As String = DecodeToken(token, cryptKey)

			If (Not String.IsNullOrEmpty(stoken)) Then
				stoken = ValidateToken(stoken, signKey)
			End If

			Return stoken
		End Function

		''' <summary>
		''' Decode the given token. Returns null on failure.
		''' </summary>
		'''
		''' <list type="number">
		''' <item>First, the string is URL unescaped and base64
		''' decoded.</item>
		''' <item>Second, the IV is extracted from the first 16 bytes
		''' of the string.</item>
		''' <item>Finally, the string is decrypted by using the
		''' encryption key.</item> 
		''' </list>
		''' <param name="token">Raw token.</param>
		''' <returns>Decoded token.</returns>
		Public Function DecodeToken(ByVal token As String) As String
			Return DecodeToken(token, cryptKey)
		End Function

		''' <summary>
		''' Decode the given token. Returns null on failure.
		''' </summary>
		'''
		''' <list type="number">
		''' <item>First, the string is URL unescaped and base64
		''' decoded.</item>
		''' <item>Second, the IV is extracted from the first 16 bytes
		''' of the string.</item>
		''' <item>Finally, the string is decrypted by using the
		''' encryption key.</item> 
		''' </list>
		''' <param name="token">Raw token.</param>
		''' <param name="cryptKey">Crypt key.</param>
		''' <returns>Decoded token.</returns>
		Public Function DecodeToken(ByVal token As String, ByVal cryptKey() As Byte) As String
			If cryptKey Is Nothing OrElse cryptKey.Length = 0 Then
				Throw New InvalidOperationException("Error: DecodeToken: Secret key was not set. Aborting.")
			End If

			If String.IsNullOrEmpty(token) Then
				debug("Error: DecodeToken: Null token input.")
				Return Nothing
			End If

			Const ivLength As Integer = 16
			Dim ivAndEncryptedValue() As Byte = u64(token)

			If (ivAndEncryptedValue Is Nothing) OrElse (ivAndEncryptedValue.Length <= ivLength) OrElse ((ivAndEncryptedValue.Length Mod ivLength) <> 0) Then
				debug("Error: DecodeToken: Attempted to decode invalid token.")
				Return Nothing
			End If

			Dim aesAlg As Rijndael = Nothing
			Dim memStream As MemoryStream = Nothing
			Dim cStream As CryptoStream = Nothing
			Dim sReader As StreamReader = Nothing
			Dim decodedValue As String = Nothing

			Try
				aesAlg = New RijndaelManaged()
				aesAlg.KeySize = 128
				aesAlg.Key = cryptKey
				aesAlg.Padding = PaddingMode.PKCS7
				memStream = New MemoryStream(ivAndEncryptedValue)
				Dim iv(ivLength - 1) As Byte
				memStream.Read(iv, 0, ivLength)
				aesAlg.IV = iv
				cStream = New CryptoStream(memStream, aesAlg.CreateDecryptor(), CryptoStreamMode.Read)
				sReader = New StreamReader(cStream, Encoding.ASCII)
				decodedValue = sReader.ReadToEnd()
			Catch e As Exception
                debug("Error: DecodeToken: Decryption failed: " & e.ToString())
				Return Nothing
			Finally
				Try
					If sReader IsNot Nothing Then
						sReader.Close()
					End If
					If cStream IsNot Nothing Then
						cStream.Close()
					End If
					If memStream IsNot Nothing Then
						memStream.Close()
					End If
					If aesAlg IsNot Nothing Then
						aesAlg.Clear()
					End If
				Catch e As Exception
                    debug("Error: DecodeToken: Failure during resource cleanup: " & e.ToString())
				End Try
			End Try

			Return decodedValue
		End Function

		''' <summary>
		''' Creates a signature for the given string.
		''' </summary>
		Public Function SignToken(ByVal token As String) As Byte()
			Return SignToken(token, signKey)
		End Function

		''' <summary>
		''' Creates a signature for the given string by using the
		''' signature key.
		''' </summary>
		Public Function SignToken(ByVal token As String, ByVal signKey() As Byte) As Byte()
			If signKey Is Nothing OrElse signKey.Length = 0 Then
				Throw New InvalidOperationException("Error: SignToken: Secret key was not set. Aborting.")
			End If

			If String.IsNullOrEmpty(token) Then
				debug("Attempted to sign null token.")
				Return Nothing
			End If

			Using hashAlg As HashAlgorithm = New HMACSHA256(signKey)
				Dim data() As Byte = Encoding.Default.GetBytes(token)
				Dim hash() As Byte = hashAlg.ComputeHash(data)
				Return hash
			End Using
		End Function

		''' <summary>
		''' Extracts the signature from the token and validates it.
		''' </summary>
		''' <param name="token"></param>
		''' <returns></returns>
		Public Function ValidateToken(ByVal token As String) As String
			Return ValidateToken(token, signKey)
		End Function

		''' <summary>
		''' Extracts the signature from the token and validates it by using the 
		''' signature key.
		''' </summary>
		Public Function ValidateToken(ByVal token As String, ByVal signKey() As Byte) As String
			If String.IsNullOrEmpty(token) Then
				debug("Error: ValidateToken: Null token.")
				Return Nothing
			End If

			Dim s() As String = { "&sig=" }
			Dim bodyAndSig() As String = token.Split(s, StringSplitOptions.None)

			If bodyAndSig.Length <> 2 Then
				debug("Error: ValidateToken: Invalid token: " & token)
				Return Nothing
			End If

			Dim sig() As Byte = u64(bodyAndSig(1))

			If sig Is Nothing Then
				debug("Error: ValidateToken: Could not extract the signature from the token.")
				Return Nothing
			End If

			Dim sig2() As Byte = SignToken(bodyAndSig(0), signKey)

			If sig2 Is Nothing Then
				debug("Error: ValidateToken: Could not generate a signature for the token.")
				Return Nothing
			End If

			If sig.Length = sig2.Length Then
				For i As Integer = 0 To sig.Length - 1
					If sig(i) <> sig2(i) Then
						GoTo badSig
					End If
				Next i

				Return token
			End If

		badSig:
			debug("Error: ValidateToken: Signature did not match.")
			Return Nothing
		End Function

'         Implementation of the methods needed to perform Windows Live
'           application verification as well as trusted sign-in. 

		''' <summary>
		''' Generates an Application Verifier token.
		''' </summary>
		Public Function GetAppVerifier() As String
			Return GetAppVerifier(Nothing)
		End Function

		''' <summary>
		''' Generates an Application Verifier token. An IP address
		''' can be included in the token.
		''' </summary>
		Public Function GetAppVerifier(ByVal ip As String) As String
            ip = IIf(String.IsNullOrEmpty(ip), String.Empty, ("&ip=" + ip))
			Dim token As String = "appid=" & AppId & "&ts=" & getTimestamp() & ip
			Dim sig As String = e64(SignToken(token))

			If String.IsNullOrEmpty(sig) Then
				debug("Error: GetAppVerifier: Failed to sign the token.")
				Return Nothing
			End If

			token &= "&sig=" & sig
			Return HttpUtility.UrlEncode(token)
		End Function

		''' <summary>
		''' Returns the URL needed to retrieve the application
		''' security token. The application security token
		''' will be generated for the Windows Live site.
		'''
		''' JavaScript Output Notation (JSON) output is returned:
		'''
		''' {"token":"&lt;value&gt;"}
		''' </summary>
		Public Function GetAppLoginUrl() As String
			Return GetAppLoginUrl(Nothing, Nothing, False)
		End Function

		''' <summary>
		''' Returns the URL needed to retrieve the application
		''' security token.
		'''
		''' By default, the application security token will be
		''' generated for the Windows Live site; a specific Site ID
		''' can optionally be specified in 'siteId'.
		'''
		''' JSON output is returned:
		'''
		''' {"token":"&lt;value&gt;"}
		''' </summary>
		Public Function GetAppLoginUrl(ByVal siteId As String) As String
			Return GetAppLoginUrl(siteId, Nothing, False)
		End Function

		''' <summary>
		''' Returns the URL needed to retrieve the application
		''' security token.
		'''
		''' By default, the application security token will be
		''' generated for the Windows Live site; a specific Site ID
		''' can optionally be specified in 'siteId'. The IP address
		''' can also optionally be included in 'ip'.
		'''
		''' JSON output is returned:
		'''
		''' {"token":"&lt;value&gt;"}
		''' </summary>
		Public Function GetAppLoginUrl(ByVal siteId As String, ByVal ip As String) As String
			Return GetAppLoginUrl(siteId, ip, False)
		End Function

		''' <summary>
		''' Returns the URL needed to retrieve the application
		''' security token.
		'''
		''' By default, the application security token will be
		''' generated for the Windows Live site; a specific Site ID
		''' can optionally be specified in 'siteId'. The IP address
		''' can also optionally be included in 'ip'.
		'''
		''' If 'js' is false, then JSON output is returned: 
		'''
		''' {"token":"&lt;value&gt;"}
		'''
		''' Otherwise, a JavaScript response is returned. It is assumed
		''' that WLIDResultCallback is a custom function implemented to
		''' handle the token value:
		''' 
		''' WLIDResultCallback("&lt;tokenvalue&gt;");
		''' </summary>
		Public Function GetAppLoginUrl(ByVal siteId As String, ByVal ip As String, ByVal js As Boolean) As String
			Dim algPart As String = "&alg=" & SecurityAlgorithm
			Dim sitePart As String = IIf(String.IsNullOrEmpty(siteId),String.Empty,"&id=" + siteId)
            Dim jsPart As String = IIf((Not js), String.Empty, "&js=1")
            Dim url As String = SecureUrl & "wapplogin.srf?app=" & GetAppVerifier(ip) & algPart & sitePart & jsPart
			Return url
		End Function

		''' <summary>
		''' Retrieves the application security token for application
		''' verification from the application sign-in URL. The
		''' application security token will be generated for the
		''' Windows Live site.
		''' </summary>
		Public Function GetAppSecurityToken() As String
			Return GetAppSecurityToken(Nothing, Nothing)
		End Function

		''' <summary>
		''' Retrieves the application security token for application
		''' verification from the application sign-in URL.
		'''
		''' By default, the application security token will be
		''' generated for the Windows Live site; a specific Site ID
		''' can optionally be specified in 'siteId'.
		''' </summary>
		Public Function GetAppSecurityToken(ByVal siteId As String) As String
			Return GetAppSecurityToken(siteId, Nothing)
		End Function

		''' <summary>
		''' Retrieves the application security token for application
		''' verification from the application sign-in URL.
		'''
		''' By default, the application security token will be
		''' generated for the Windows Live site; a specific Site ID
		''' can optionally be specified in 'siteId'. The IP address
		''' can also optionally be included in 'ip'.
		'''
		''' Implementation note: The application security token is
		''' downloaded from the application sign-in URL in JSON format
		''' {"token":"&lt;value&gt;"}, so we need to extract
		''' &lt;value&gt; from the string and return it as seen here.
		''' </summary>
		Public Function GetAppSecurityToken(ByVal siteId As String, ByVal ip As String) As String
			Dim url As String = GetAppLoginUrl(siteId, ip)
			Dim body As String = fetch(url)
			If String.IsNullOrEmpty(body) Then
				debug("Error: GetAppSecurityToken: Failed to download token.")
				Return Nothing
			End If

			Dim re As New Regex("{""token"":""(.*)""}")
			Dim gc As GroupCollection = re.Match(body).Groups

			If gc.Count <> 2 Then
				debug("Error: GetAppSecurityToken: Failed to extract token: " & body)
				Return Nothing
			End If

			Dim cc As CaptureCollection = gc(1).Captures

			If cc.Count <> 1 Then
				debug("Error: GetAppSecurityToken: Failed to extract token: " & body)
				Return Nothing
			End If

			Return cc(0).ToString()
		End Function

		''' <summary>
		''' Returns a string that can be passed to the GetTrustedParams
		''' function as the 'retcode' parameter. If this is specified as
		''' the 'retcode', then the app will be used as return URL
		''' after it finishes trusted sign-in.  
		''' </summary>
		Public Function GetAppRetCode() As String
			Return "appid=" & AppId
		End Function

		''' <summary>
		''' Returns a table of key-value pairs that must be posted to
		''' the sign-in URL for trusted sign-in. Use HTTP POST to do
		''' this. Be aware that the values in the table are neither
		''' URL nor HTML escaped and may have to be escaped if you are
		''' inserting them in code such as an HTML form.
		''' 
		''' The user to be trusted on the local site is passed in as
		''' string 'user'.
		''' </summary>
		Public Function GetTrustedParams(ByVal user As String) As NameValueCollection
			Return GetTrustedParams(user, Nothing)
		End Function

		''' <summary>
		''' Returns a table of key-value pairs that must be posted to
		''' the sign-in URL for trusted sign-in. Use HTTP POST to do
		''' this. Be aware that the values in the table are neither
		''' URL nor HTML escaped and may have to be escaped if you are
		''' inserting them in code such as an HTML form.
		''' 
		''' The user to be trusted on the local site is passed in as
		''' string 'user'.
		''' 
		''' Optionally, 'retcode' specifies the resource to which
		''' successful sign-in is redirected, such as Windows Live Mail,
		''' and is typically a string in the format 'id=2000'. If you
		''' pass in the value from GetAppRetCode instead, sign-in will
		''' be redirected to the application. Otherwise, an HTTP 200
		''' response is returned.
		''' </summary>
		Public Function GetTrustedParams(ByVal user As String, ByVal retcode As String) As NameValueCollection
			Dim token As String = GetTrustedToken(user)

			If String.IsNullOrEmpty(token) Then
				Return Nothing
			End If

			token = "<wst:RequestSecurityTokenResponse xmlns:wst=""http://schemas.xmlsoap.org/ws/2005/02/trust""><wst:RequestedSecurityToken><wsse:BinarySecurityToken xmlns:wsse=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">" & token & "</wsse:BinarySecurityToken></wst:RequestedSecurityToken><wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy""><wsa:EndpointReference xmlns:wsa=""http://schemas.xmlsoap.org/ws/2004/08/addressing""><wsa:Address>uri:WindowsLiveID</wsa:Address></wsa:EndpointReference></wsp:AppliesTo></wst:RequestSecurityTokenResponse>"

			Dim nvc As New NameValueCollection(3)
			nvc("wa") = SecurityAlgorithm
			nvc("wresult") = token

			If retcode IsNot Nothing Then
				nvc("wctx") = retcode
			End If

			Return nvc
		End Function

		''' <summary>
		''' Returns the trusted sign-in token in the format needed by the
		''' trusted sign-in gadget.
		'''
		''' User to be trusted on the local site is passed in as string
		''' 'user'.
		''' </summary>
		Public Function GetTrustedToken(ByVal user As String) As String
			If String.IsNullOrEmpty(user) Then
				debug("Error: GetTrustedToken: Invalid user specified.")
				Return Nothing
			End If

			Dim token As String = "appid=" & AppId & "&uid=" & HttpUtility.UrlEncode(user) & "&ts=" & getTimestamp()
			Dim sig As String = e64(SignToken(token))

			If String.IsNullOrEmpty(sig) Then
				debug("Error: GetTrustedToken: Failed to sign the token.")
				Return Nothing
			End If

			token &= "&sig=" & sig
			Return HttpUtility.UrlEncode(token)
		End Function

		''' <summary>
		''' Returns the trusted sign-in URL to use for the Windows Live
		''' Login server. 
		''' </summary>
		Public Function GetTrustedLoginUrl() As String
			Return SecureUrl & "wlogin.srf"
		End Function

		''' <summary>
		''' Returns the trusted sign-out URL to use for the Windows Live
		''' Login server. 
		''' </summary>
		Public Function GetTrustedLogoutUrl() As String
			Return SecureUrl & "logout.srf?appid=" & AppId
		End Function

		' Helper methods 

		''' <summary>
		''' Function to parse the settings file.
		''' </summary>
		''' <param name="settingsFile"></param>
		''' <returns></returns>
		Private Shared Function parseSettings(ByVal settingsFile As String) As NameValueCollection
			If String.IsNullOrEmpty(settingsFile) Then
				Throw New ArgumentNullException("settingsFile")
			End If

			' Throws an exception on any failure.
			Dim xd As New XmlDocument()
			xd.Load(settingsFile)

			Dim topNode As XmlNode = xd.SelectSingleNode("//windowslivelogin")

			If topNode Is Nothing Then
				Throw New XmlException("Error: parseSettings: Failed to parse settings file: " & settingsFile)
			End If

			Dim settings As New NameValueCollection()
			Dim children As IEnumerator = topNode.GetEnumerator()

			Do While children.MoveNext()
				Dim child As XmlNode = CType(children.Current, XmlNode)
				settings(child.Name) = child.InnerText
			Loop

			Return settings
		End Function

		''' <summary>
		''' Derives the key, given the secret key and prefix as described in the 
		''' Web Authentication SDK documentation.
		''' </summary>
		Private Shared Function derive(ByVal secret As String, ByVal prefix As String) As Byte()
			Using hashAlg As HashAlgorithm = HashAlgorithm.Create("SHA256")
				Const keyLength As Integer = 16
				Dim data() As Byte = Encoding.Default.GetBytes(prefix & secret)
				Dim hashOutput() As Byte = hashAlg.ComputeHash(data)
				Dim byteKey(keyLength - 1) As Byte
				Array.Copy(hashOutput, byteKey, keyLength)
				Return byteKey
			End Using
		End Function

		''' <summary>
		''' Parses query string and return a table representation of 
		''' the key and value pairs.  Similar to 
		''' HttpUtility.ParseQueryString, except that no URL decoding
		''' is done and only the last value is considered in the case
		''' of multiple values with one key.
		''' </summary>
		Private Shared Function parse(ByVal input As String) As NameValueCollection
			If String.IsNullOrEmpty(input) Then
				debug("Error: parse: Null input.")
				Return Nothing
			End If

			Dim pairs As New NameValueCollection()

			Dim kvs() As String = input.Split(New Char(){"&"c})
			For Each kv As String In kvs
				Dim separator As Integer = kv.IndexOf("="c)

				If (separator = -1) OrElse (separator = kv.Length) Then
					debug("Warning: parse: Ignoring pair: " & kv)
					Continue For
				End If

				pairs(kv.Substring(0, separator)) = kv.Substring(separator+1)
			Next kv

			Return pairs
		End Function

		''' <summary>
		''' Generates a timestamp suitable for the application
		''' verifier token.
		''' </summary>
		Private Shared Function getTimestamp() As String
			Dim refTime As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
			Dim ts As TimeSpan = DateTime.UtcNow.Subtract(refTime)
			Return (CUInt(ts.TotalSeconds)).ToString()
		End Function

		''' <summary>
		''' Base64-encodes and URL-escapes a byte array.
		''' </summary>
		Private Shared Function e64(ByVal b() As Byte) As String
			Dim s As String = Nothing
			If b Is Nothing Then
				Return s
			End If

			Try
				s = Convert.ToBase64String(b)
				s = HttpUtility.UrlEncode(s)
			Catch e As Exception
                debug("Error: e64: Base64 conversion error: " & e.ToString())
			End Try

			Return s
		End Function

		''' <summary>
		''' URL-unescapes and Base64-decodes a string.
		''' </summary>
		Private Shared Function u64(ByVal s As String) As Byte()
			Dim b() As Byte = Nothing
			If s Is Nothing Then
				Return b
			End If
			s = HttpUtility.UrlDecode(s)

			Try
				b = Convert.FromBase64String(s)
			Catch e As Exception
                debug("Error: u64: Base64 conversion error: " & s & ", " & e.ToString())
			End Try
			Return b
		End Function

		''' <summary>
		''' Fetches the contents given a URL.
		''' </summary>
		Private Shared Function fetch(ByVal url As String) As String
			Dim body As String = Nothing
			Try
				Dim req As WebRequest = HttpWebRequest.Create(url)
				req.Method = "GET"
				Dim res As WebResponse = req.GetResponse()
				Using sr As New StreamReader(res.GetResponseStream(), Encoding.UTF8)
					body = sr.ReadToEnd()
				End Using
			Catch e As Exception
                debug("Error: fetch: Failed to get the document: " & url & ", " & e.ToString())
			End Try
			Return body
        End Function
    End Class
End Namespace
